/**
 * 通用并发闸门：按 key 限制并发，并提供队列排队
 */
// legacy static ConcurrencyGate removed in favor of instance-based implementation below

/**
 * 队列项
 * - 包含 resolve 和 reject 函数
 * - 包含 timeout 定时器
 */
interface QueueItem {
  resolve: (release: () => void) => void
  reject: (err: Error) => void
  timeout: any
}

/**
 * 并发控制器
 * - 限制同时执行的任务数量
 * - 支持队列化等待
 * - 支持冷却时间
 */
class ConcurrencyGateImpl {
  private active: Map<string, number> = new Map()
  private limit: Map<string, number> = new Map()
  private queues: Map<string, QueueItem[]> = new Map()
  private cooldowns: Map<string, number> = new Map() // key -> until ts(ms)

  private getActive(key: string): number { return this.active.get(key) || 0 }
  private setActive(key: string, n: number): void { this.active.set(key, Math.max(0, n)) }
  private getQueue(key: string): QueueItem[] {
    let q = this.queues.get(key)
    if (!q) { q = []; this.queues.set(key, q) }
    return q
  }

  setLimit(key: string, maxConcurrent: number): void {
    const n = Math.max(1, Math.floor(maxConcurrent))
    this.limit.set(key, n)
  }

  getLimit(key: string): number { return this.limit.get(key) || 1 }

  setCooldown(key: string, ms: number): void {
    const until = Date.now() + Math.max(0, Math.floor(ms))
    this.cooldowns.set(key, until)
  }

  getRemainingCooldownMs(key: string): number {
    const until = this.cooldowns.get(key) || 0
    const left = until - Date.now()
    return left > 0 ? left : 0
  }

  async acquire(key: string, options?: { limit?: number, timeoutMs?: number }): Promise<() => void> {
    if (options?.limit)
      this.setLimit(key, options.limit)
    const limit = this.getLimit(key)
    const cooldownLeft = this.getRemainingCooldownMs(key)
    if (cooldownLeft > 0)
      throw new Error(`cooldown:${Math.ceil(cooldownLeft / 1000)}s`)

    const tryAcquireNow = (): (() => void) | null => {
      const cur = this.getActive(key)
      if (cur < limit) {
        this.setActive(key, cur + 1)
        let released = false
        const release = () => {
          if (released)
            return
          released = true
          const now = this.getActive(key)
          this.setActive(key, now - 1)
          const q = this.getQueue(key)
          const next = q.shift()
          if (next) {
            clearTimeout(next.timeout)
            // grant to next
            this.setActive(key, this.getActive(key) + 1)
            let done = false
            const rel2 = () => {
              if (done)
                return
              done = true
              const cur2 = this.getActive(key)
              this.setActive(key, cur2 - 1)
              const q2 = this.getQueue(key)
              const n2 = q2.shift()
              if (n2) {
                clearTimeout(n2.timeout)
                this.setActive(key, this.getActive(key) + 1)
                n2.resolve(rel2)
              }
            }
            next.resolve(rel2)
          }
        }
        return release
      }
      return null
    }

    const immediate = tryAcquireNow()
    if (immediate)
      return immediate

    const timeoutMs = Math.max(0, Math.floor(options?.timeoutMs ?? 15000))
    return await new Promise<() => void>((resolve, reject) => {
      const q = this.getQueue(key)
      const item: QueueItem = {
        resolve,
        reject,
        timeout: setTimeout(() => {
          // 超时出队
          const idx = q.indexOf(item)
          if (idx >= 0)
            q.splice(idx, 1)
          reject(new Error('acquire_timeout'))
        }, timeoutMs),
      }
      q.push(item)
    })
  }
}

export const ConcurrencyGate = new ConcurrencyGateImpl()
